package engine

type ConcurrentEngine struct {
	Scheduler Scheduler
	WorkerCount int
	ItemChan         chan Item
	RequestProcessor Processor
}

type Processor func(Request) (ParserResult, error)

//duck typing: 不管接口如何组合，只需要实现了这四个方法，就是实现了这个接口
type Scheduler interface {
	ReadyNotifier
	Submit(Request)
	WorkerChan() chan Request
	Run()
}

//如果实现了Scheduler接口， 那么也就实现了这个接口
//当调用方传入Scheduler的实现时候， 被调用方可以用ReadyNotifier接收
type ReadyNotifier interface {
	WorkerReady (chan Request)
}
func (e *ConcurrentEngine) Run(seeds ...Request) {
	out := make(chan ParserResult)

	//QueuedScheduler#Run
	//将Request放入Request chan中，并且保证chan有足够的时间去处理当前Request,
	//当处理完当前Request后，立即处理下一个Request
	e.Scheduler.Run()

	//2 创建10个worker拉取连接并解析,每个worker从自己的Request chan中拉取数据，
	// 解析后，放入out channel当中
	for i:=0; i<e.WorkerCount;i++{
		//e.Scheduler.WorkerChan()调用的是 QueuedScheduler#WorkerChan,
		//创建一个自己的Request chan
		//然后createWorker方法中，将自己的Request chan放入同一个chan当中（即workerChan）
		e.createWorker(e.Scheduler.WorkerChan(),out,e.Scheduler)
	}

	//1 每个种子放入一个channel, 并将种子拉取到channel in当中
	for _,r:= range seeds{
		if isDuplicateUrl(r.Url){
			continue
		}
		//真正将一个engine.Request(在main函数已经定义)放入request Chan中，开始触发流程
		e.Scheduler.Submit(r)
	}

	for {
		//3 从out（在createWorker方法中放入）拉取ParserResult，ParserResult中的Items可能有多个
		result := <-out
		for _,item:= range result.Items{
			go func(i Item) {
				// 放入item chan, 给itemsaver拉取后,远程调用保存到ES
				e.ItemChan <- i
			}(item)
		}

		for _,r:= range result.Requests{
			//去重
			if isDuplicateUrl(r.Url){
				continue
			}
			//真正将一个engine.Request(在main函数已经定义)放入request Chan中，开始触发流程
			e.Scheduler.Submit(r)
		}
	}
}

func (e *ConcurrentEngine) createWorker(in chan Request,
	out chan ParserResult,
	ready ReadyNotifier)  {
	go func() {
		for {
			//所有的Request chan都放入worker chan
			//这里放入worker chan后，会按照之前e.Scheduler.Run()定义好的规则
			//将Request chan放入队列workerQ []chan engine.Request中
			//到目前为止，Request chan还没放入数据，只是定义好规范
			ready.WorkerReady(in)
			//从Request chan拉一个Request出来，作为后面远程调用的一个入参
			//到目前为止，Request chan还没放入数据，只是定义好规范
			request := <-in
			//拉取出request后
			//调起在main函数里定义好的Processor(即远程调用解析网页过程)
			//这里也	只是定义好规范
			result, err := e.RequestProcessor(request)
			if err != nil {
				continue
			}
			//将结果放入chan 以便发起远程调用去保存数据
			out <- result
		}
	}()
}

var visitedUrls = make(map[string]bool)
func isDuplicateUrl(url string)  bool{
	if visitedUrls[url]{
		return true
	}
	visitedUrls[url] = true
	return false
}
